/**************************************************************************************
*  j_thread.c
*
*  This file contains thread support functions.
*
**************************************************************************************
*
*  The contents of this file are subject to the RTJ COMPUTING non-commercial
*  source code license agreement ("License"); you may not use this file except in
*  compliance with the License. A copy of the License (license.terms) can be
*  found in the root of installation folder or on the web at
*  http://www.rtjcom.com/Files/license.terms
*
*  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
*  OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
*  MERCHANTABILITY,  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
*  IN NO EVENT SHALL RTJ COMPUTING BE LIABLE FOR ANY CLAIM, DAMAGES
*  OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
*  ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
*  OTHER DEALINGS IN THE SOFTWARE.
*
*  Copyright (c) 2000-2002 RTJ Computing Pty. Ltd. All rights reserved.
*
**************************************************************************************/

#include "jcommon.h"
#include "jvm.h"


static bitem_t findRunMethod(bitem_t cl);
#if ENABLE_THREADS
static thread_t *last_thread = NULL;
static thread_t *nextThread(thread_t *thread);
static thread_t *scheduleNextThread(void);
static bool releaseClassLock(void);
#endif



/****************************************************************************
* Initialises the threads related variables and adds main application thread
* into thread table.
*
* Input:        none
* Output:       none
* Return:       true    if successfull
*               false   if exception has been generated
****************************************************************************/
bool initThreads1(void)
{
    uint16 i;

    thr_switch = 0;
    thr_count = 0;
    thr_active = threads;
    ev_thread = NULL;

    /* assign unique ID to each entry in thread table */
    for (i=0; i < config->threads; i++)
        threads[i].id = (uint8)(i + 1);

    /* add new entry to thread table for main() method */
    thr_active->objref = createObject(APP->thread);
    if (thr_active->objref != NULL )
    {
        thr_active->flag |= FLG_USED;
        return true;
    }
    return false;
}


/****************************************************************************
* Run <init>() method on main Thread object
*
* Input:        none
* Output:       none
* Return:       true    if successfull
*               false   if exception has been generated
****************************************************************************/
bool initThreads2(void)
{
    if (run_init(thr_active->objref, NULL))
    {
        thr_count++;
        return true;
    }
    return false;
}


/****************************************************************************
* Creates new entry in thr_table if there is enough room. If table is full
* EXCEP_ThreadExecFail is thrown.
* Then it checks if run() method exists in the class which address has been
* provided in X. If class doesn't contain run() EXCEP_ThreadStartupFail will
* be thrown.
* Once run() has been located the new thr_table entry is created and appropriate
* fields are setup and run() will begin execution.
*
* Input:        ref     reference to class object containing the run() method
* Output:       none
* Return:       true    if successfull
*               false   if exception has been generated
****************************************************************************/
bool createThread(ref_t *ref)
{
    uint16 i;
    bitem_t run;

    run = findRunMethod(ref->class_ptr);
    if (run.ptr == NULL)
    {
        if (vm_excep == NO_EXCEP)
            vm_excep = INTERR_ThreadSetupFail;
        return false;
    }

    /* save current pc/sp values */
    thr_active->curr_frame->pc = vm_pc;
    thr_active->curr_frame->sp = vm_sp;

    /* find next free entry in threads table */
    for (i=0; i < config->threads; i++)
    {
        if (threads[i].flag == 0)
            break;
    }
    if (i == config->threads)
    {
        vm_excep = INTERR_ThreadLimit;
        return false;
    }

    thr_count++;
    thr_active = &threads[i];
    thr_active->objref = ref;
    thr_active->flag = FLG_USED;
    thr_active->curr_frame = NULL;

    if (!createFrame(run))
        return false;

    thr_active->curr_frame->locals[0].i = PUT_REF(ref);
    thr_switch = 0;

    return true;
}


/****************************************************************************
* Searches for the run method in current class. I not found then it will
* traverse trough references to super classes.
*
* Input:        cl      pointer to current class
* Output:       none
* Return:       location of run() method if found, otherwise NULL
*               is returned
****************************************************************************/
static bitem_t findRunMethod(bitem_t cl)
{
    bitem_t run;
    bool ok = true;

    run.ptr = NULL;
    while (true)
    {
        config->switchBank(cl.bank);
        if (((class_t *)cl.ptr)->run != NULL)
            break;

        if (((class_t *)cl.ptr)->super.ptr == NULL)
        {
            ok = false;
            break;
        }

        cl.ptr = BANK_ABS_LOC(((class_t *)cl.ptr)->super.ptr);
        cl.bank = ((class_t *)cl.ptr)->super.bank;
    }
    if (ok)
    {
        run.ptr = BANK_ABS_LOC(((class_t *)cl.ptr)->run);
        run.bank = cl.bank;
        config->switchBank(run.bank);
        if (((method_t *)run.ptr)->flags & ACC_ABSTRACT)
        {
            vm_excep = ERROR_AbstractMethod;
            run.ptr = NULL;
        }
    }

    config->switchBank(vm_curr_bank);
    return run;
}


/****************************************************************************
* Removes currently running thread from threads table.
*
* Input:        none
* Output:       none
* Return:       none
****************************************************************************/
void deleteThread(thread_t *thread)
{
#if ENABLE_THREADS
    uint16 i;
#endif

    if (thr_count > 0 && thread != NULL)
    {
        thr_count--;
        thread->flag = 0;

#if ENABLE_THREADS
        /* if event thread has been deleted then clear pointer to it */
        if (thread == ev_thread)
            ev_thread = NULL;

        /* check if there are any non-daemon threads running */
        for (i=0; i < config->threads; i++)
        {
            if (threads[i].flag && !(threads[i].objref->ptr->flag & OF_DAEMON))
                break;
        }
        /* stop all daemon threads if this was the last running non-daemon thread */
        if (i == config->threads)
        {
            for (i=0; i < config->threads; i++)
                threads[i].flag = 0;
            thr_count = 0;
        }
        else if (thread == thr_active)
        {
            /* force thread switch after removing thread from table*/
            thr_switch++;
        }
#endif
    }
}


#if ENABLE_THREADS
/****************************************************************************
* Finds the thread in the thread table matches the specified class reference.
*
* Input:        ref      reference to class object
* Output:       none
* Return:       ptr. to thread for matching class
*               NULL if not found
****************************************************************************/
thread_t *findThread(ref_t *ref)
{
    uint16 i;

    for (i=0; i < config->threads; i++)
    {
        if (threads[i].flag && threads[i].objref == ref)
            return &threads[i];
    }

    return NULL;
}

/****************************************************************************
* Gets next entry in the thread table.
*
* Input:        thread  starting position in thread table
* Output:       none
* Return:       ptr. to next thread in table
****************************************************************************/
static thread_t *nextThread(thread_t *thread)
{
    if (thread == &threads[config->threads-1])
        return &threads[0];

    return (thread_t *)((uint8 *)thread + sizeof(thread_t));
}

/****************************************************************************
* Goes trought the event fire counters to see if one contains unhandled event.
*
* Input:    none
* Output:   none
* Return:   false   - if events thread is not ready to be resumed
*           true    - when event thread should be resumed
****************************************************************************/
static bool isEventPending(void)
{
    uint16 i;

    if (ev_thread != NULL && (ev_thread->flag & FLG_SUSPEND) == FLG_SUSPEND)
    {
        for (i=0; i < config->events; i++)
        {
            if (config->ev_counters[i] > 0)
            {
                ev_thread->flag &= ~FLG_SUSPEND;
                ev_index = i;
                return true;
            }
        }
    }

    return false;
}


/****************************************************************************
* Finds the next thread in the thread table that can be scheduled to run.
* If notify idle state is enabled then this function will also detect VM
* idle state and signals this via vm_config_t.vmIdle function. The VM enters
* idle state when all threads are either suspended, sleeping or in a wait
* state.
*
* Input:        none
* Output:       none
* Return:       ptr. to thread that can start running
****************************************************************************/
static thread_t *scheduleNextThread(void)
{
    thread_t *thread;
#if ENABLE_IDLE_NOTIFY
    thread_t *start_thread;
    bool idle = false;
#endif

    if (last_thread != NULL)
    {
        /* restore the normal thread scheduling after running the event thread */
        thread = last_thread;
        last_thread = NULL;
    }
    else
        thread = thr_active;

#if ENABLE_IDLE_NOTIFY
    start_thread = thread;
#endif

    /* loop here until one of the threads becomes available to run */
    while (true)
    {
#if ENABLE_WDT
        if (config->clearWdt)
            config->clearWdt();
#endif
        /* check if there any pending events, if so then resume events thread */
        if (isEventPending())
        {
            /*
            ** Remember the thread that has run prior the event thread.
            ** Failure to do so may cause some threads not to execute at all
            ** if the event thread is activated frequently.
            */
            last_thread = thr_active;
            thread = ev_thread;
            break;
        }

        /* get thread following the active thread in thread table */
        thread = nextThread(thread);

        /* threads with other than FLG_USED flag set must remain suspended */
        if (thread->flag == FLG_USED)
            break;

#if ENABLE_IDLE_NOTIFY
        /*
        ** If after a loop over all threads - we have not found
        ** a thread ready-to-run, signal that the VM is idle.
        */
        if (thread == start_thread && !idle && config->idleNotify)
        {
            config->idleNotify(true);
            idle = true;
        }
#endif
    }

#if ENABLE_IDLE_NOTIFY
    /* notify VM exit out of idle state */
    if (idle && config->idleNotify)
        config->idleNotify(false);
#endif
    return thread;
}


/****************************************************************************
* Checks if a thread switch is required. It checks thr_switch variable and
* if the value is greater than zero and there is more than one thread in the
* thr_table, then the execution of the next waiting thread is resumed.
* If the waiting thread was suspended or the sleep counter is not zero then
* the next waiting thread is resumed.
*
* Input:        none
* Output:       none
* Return:       none
****************************************************************************/
void switchThreads(void)
{
    thread_t *thread;

    if (thr_switch == 0 || (vm_status & WIDE_FLAG))
        return;

    thread = scheduleNextThread();
    if (thread != NULL)
    {
        thr_switch = 0;
        if (thread != thr_active)
        {
            thr_active->curr_frame->pc = vm_pc;
            thr_active->curr_frame->sp = vm_sp;

            thr_active = thread;
            vm_pc = thread->curr_frame->pc;
            vm_sp = thread->curr_frame->sp;
            vm_curr_bank = (uint8)thread->curr_frame->method.bank;
            config->switchBank(vm_curr_bank);
        }
    }
}
#endif


/****************************************************************************
* Goes trough all used thread entries and if THR_SLEEP field is not zero
* then it decrements its value.
*
* Input:        none
* Output:       none
* Return:       none
****************************************************************************/
static void updateSleepCounters(void)
{
    uint16 i, cnt;
    thread_t *thread;
#if ENABLE_THREADS
    ref_t *ref;
#endif

    thread = &threads[0];
    cnt = config->threads;
    for (i=0; i < cnt; i++)
    {
        if (thread->flag & FLG_USED)
        {
            if (thread->flag & FLG_SLEEP)
            {
                thread->sleep -= config->time_slice;
                if (thread->sleep <= 0)
                {
                    thread->sleep = 0;
                    thread->flag &= ~FLG_SLEEP;
                }
            }
#if ENABLE_THREADS
            else if (thread->flag & FLG_WAIT_NOTIFY && thread->wait > 0)
            {
                thread->wait -= config->time_slice;
                if (thread->wait <= 0)
                {
                    thread->wait = 0;
                    thread->flag &= ~FLG_WAIT_NOTIFY;
                    ref = thread->wait_obj;
                    thread->wait_obj = NULL;
                    acquireObjectLock(thread, ref);
                }
            }
#endif
        }
        thread = (thread_t *)((uint8 *)thread + sizeof(thread_t));
    }
}

/****************************************************************************
* Updates all running software timers. If a timer value expires then timer fire
* counter is incremented.
*
* Input:        none
* Output:       none
* Return:       none
****************************************************************************/
static void updateTimers(void)
{
    uint16 i;

    for (i=0; i < config->timers; i++)
    {
        if (sw_timers[i].time > 0)
        {
            sw_timers[i].time -= config->time_slice;
            if (sw_timers[i].time <= 0)
            {
                sw_timers[i].time = 0;
                sw_timers[i].flag = TMR_EXPIRED;
                if (ev_thread != NULL)
                {
                    /* increment timer's fire counter and force events thread to run */
                    config->ev_counters[TMR_FIRE_COUNTER]++;
                    thr_switch++;
                }
            }
        }
    }
}


/***************************************************************************
* Real-time timer interrupt service routine. Forces thread switching by
* incrementing the thr_switch counter.
* Call routines to update Thread.sleep and Timer counters.
*
* Input:        none
* Output:       none
* Return:       none
****************************************************************************/
void vmTimeSlice(void)
{
    /* make threads to switch */
    thr_switch++;

    updateSleepCounters();
    updateTimers();

    vm_time_ms += config->time_slice;
}

#if ENABLE_THREADS
/***************************************************************************
* Sets the threads position in the queue of threads waiting for lock release.
*
* Input:        thread  - pointer to thread for which to set the queue position
*               ref     - object reference that has this lock. If NULL specified
*                         then lock is class wide.
* Output:       none
* Return:       none
****************************************************************************/
void setWaitingPos(thread_t *thread, ref_t *ref)
{
    uint16 i, pos = 0;

    /* check if the queue position has been already allocated */
    if (thread->wait_pos > 0)
        return;

    /* place the thread at the end of the wait queue */
    for (i=0; i < config->threads; i++)
    {
        if ((threads[i].flag & (FLG_USED | FLG_WAIT_LOCK)) == (FLG_USED | FLG_WAIT_LOCK) &&
            threads[i].wait_obj == ref && pos < threads[i].wait_pos)
        {
            pos = threads[i].wait_pos;
        }
    }
    thread->wait_pos = (uint16)(pos + 1);
}

/***************************************************************************
* Clears the thread's position in the queue and decrements
* queue possition for all other threads waiting for the same lock.
*
* Input:        thread  - pointer to thread for which to clear the queue position
*               ref     - object reference that has this lock. If NULL specified
*                         then lock is class wide.
* Output:       none
* Return:       none
****************************************************************************/
void clearWaitingPos(thread_t *thread, ref_t *ref)
{
    uint16 i;

    /* decrement wait position of threads waiting for this object */
    for (i=0; i < config->threads; i++)
    {
        if ((threads[i].flag & (FLG_USED | FLG_WAIT_LOCK)) == (FLG_USED | FLG_WAIT_LOCK) &&
            threads[i].wait_obj == ref)
        {
            threads[i].wait_pos--;
        }
    }
    thread->wait_pos = 0;
}

/***************************************************************************
* Retrieves the next thread from the queue of threads waiting for lock to be
* released.
*
* Input:        ref     - object reference that has this lock. If NULL specified
*                         then lock is class wide.
* Output:       none
* Return:       Pointer to the next thread that was waiting for the lock release.
****************************************************************************/
thread_t *getWaitingThread(ref_t *ref)
{
    uint16 i, pos = 0xFFFF, index;

    /* find thread with lowest queue position */
    for (i=0; i < config->threads; i++)
    {
        if ((threads[i].flag & (FLG_USED | FLG_WAIT_LOCK)) == (FLG_USED | FLG_WAIT_LOCK) &&
            threads[i].wait_obj == ref && pos >= threads[i].wait_pos)
        {
            pos = threads[i].wait_pos;
            index = i;
        }
    }

    if (pos == 0xFFFF)
        return NULL;

    return &threads[index];
}

/***************************************************************************
* Acquires lock for the object referenced by the input parameter. If the lock
* can't be acquired then the specified thread is placed into "wait for lock" state.
*
* Input:        thread   - pointer to thread that executes method trying to
*                         acquire object's lock
*               ref      - pointer to object's reference
*                          false if lock should apply on entire class
* Output:       none
* Return:       none
****************************************************************************/
void acquireObjectLock(thread_t *thread, ref_t *ref)
{
    if (thread->wait_locks > 0 && ref->ptr->locks == 0)
    {
        ref->ptr->locks = thread->wait_locks;
        ref->ptr->thread_id = thread->id;
        thread->wait_locks = 0;
        clearWaitingPos(thread, ref);
        return;
    }
    else if (ref->ptr->locks == 0 || ref->ptr->thread_id == thread->id)
    {
        ref->ptr->locks++;
        ref->ptr->thread_id = thread->id;
        clearWaitingPos(thread, ref);
        return;
    }

    /* suspend this thread and force another thread to run */
    setWaitingPos(thread, ref);
    thread->wait_obj = ref;
    thread->flag |= FLG_WAIT_LOCK;
    thr_switch++;
}

/***************************************************************************
* Acquires class-wide lock that that prohibits all static methods from being
* executed by other threads.
* Note that class instance methods can still be executed by other threads.
* If the lock can't be acquired then the specified thread is placed into
* "wait for lock" state.
*
* Input:        thread   - pointer to thread that executes method trying to
*                         acquire lock on a class
* Output:       none
* Return:       none
****************************************************************************/
void acquireClassLock(thread_t *thread)
{
    uint16 cls_index;

    cls_index = ((class_t *)thread->curr_frame->class_ptr.ptr)->index;
    if (class_locks[cls_index].locks == 0 ||
        class_locks[cls_index].thread_id == (uint16)thread->id)
    {
        class_locks[cls_index].locks++;
        class_locks[cls_index].thread_id = thread->id;
        clearWaitingPos(thread, (ref_t *)cls_index);
    }
    else
    {
        /* suspend this thread and force another thread to run */
        setWaitingPos(thread, (ref_t *)cls_index);
        thread->flag |= FLG_WAIT_LOCK;
        thread->wait_obj = (ref_t *)cls_index;
        thr_switch++;
    }
}

/***************************************************************************
* Checks if the currently executed method acquired any locks that can be
* released so the other threads waiting on these locks can be resumed.
*
* Input:        none
* Output:       none
* Return:       true    - if locks has been releases or this thread doesn't
*                         own any locks
*               false   - if there was an error during release operation (shouldn't happen)
****************************************************************************/
bool releaseLocks(void)
{
    if ((((method_t *)thr_active->curr_frame->method.ptr)->flags & (ACC_SYNCH | ACC_STATIC)) == ACC_SYNCH)
    {
        if (!releaseObjectLock(GET_REF(thr_active->curr_frame->locals[0].val)) && !handle_excep())
            return false;
    }
    else if ((((method_t *)thr_active->curr_frame->method.ptr)->flags & (ACC_SYNCH | ACC_STATIC)) == (ACC_SYNCH | ACC_STATIC))
    {
        if (!releaseClassLock() && !handle_excep())
            return false;
    }
    return true;
}


/***************************************************************************
* Releases the lock for the object referenced by the input parameter and resumes
* execution of the first thread waiting for this lock to be released.
*
* Input:        ref     - pointer to object's reference
* Output:       none
* Return:       none
****************************************************************************/
bool releaseObjectLock(ref_t *ref)
{
    thread_t *next_thread;

    if (thr_active->id != ref->ptr->thread_id || ref->ptr->locks == 0)
    {
        vm_excep = EXCEP_IllegalMonitState;
        return false;
    }

    ref->ptr->locks--;
    if (ref->ptr->locks == 0)
    {
        ref->ptr->thread_id = 0;
        /* resume the next thread waiting for this lock to be released */
        next_thread = getWaitingThread(ref);
        if (next_thread != NULL)
        {
            next_thread->flag &= ~FLG_WAIT_LOCK;
            next_thread->wait_obj = NULL;
            acquireObjectLock(next_thread, ref);
        }
    }

    return true;
}

/***************************************************************************
* Releases the lock for the object referenced by the input parameter and resumes
* execution of the first thread waiting for this lock to be released.
*
* Input:        ref     - pointer to object's reference
* Output:       none
* Return:       none
****************************************************************************/
static bool releaseClassLock(void)
{
    thread_t *next_thread;
    uint16 cls_index;

    cls_index = ((class_t *)thr_active->curr_frame->class_ptr.ptr)->index;
    if ((uint16)thr_active->id != class_locks[cls_index].thread_id ||
        class_locks[cls_index].locks == 0)
    {
        vm_excep = EXCEP_IllegalMonitState;
        return false;
    }

    class_locks[cls_index].locks--;
    if (class_locks[cls_index].locks == 0)
    {
        class_locks[cls_index].thread_id = 0;
        /* resume the next thread waiting for this class lock to be released */
        next_thread = getWaitingThread((ref_t *)cls_index);
        if (next_thread != NULL)
        {
            next_thread->flag &= ~FLG_WAIT_LOCK;
            acquireClassLock(next_thread);
        }
    }

    return true;
}
#endif

